﻿namespace SharpGraphT.Graph;

[Serializable]
public class DefaultGraphType : IGraphType {
    private static readonly long SerialVersionUid = 4291049312119347474L;

    private readonly bool _directed;
    private readonly bool _undirected;
    private readonly bool _selfLoops;
    private readonly bool _multipleEdges;
    private readonly bool _weighted;
    private readonly bool _allowsCycles;
    private readonly bool _modifiable;

    public DefaultGraphType(bool directed, bool undirected, bool selfLoops,
        bool multipleEdges, bool weighted, bool allowsCycles, bool modifiable) {
        _directed = directed;
        _undirected = undirected;
        _selfLoops = selfLoops;
        _multipleEdges = multipleEdges;
        _weighted = weighted;
        _allowsCycles = allowsCycles;
        _modifiable = modifiable;
    }

    public bool IsDirected => _directed && !_undirected;
    public bool IsUndirected => _undirected && !_directed;
    public bool IsMixed => _undirected && _directed;
    public bool IsAllowingMultipleEdges => _multipleEdges;
    public bool IsAllowingSelfLoops => _selfLoops;
    public bool IsWeighted => _weighted;
    public bool IsAllowingCycles => _allowsCycles;
    public bool IsModifiable => _modifiable;

    public bool IsSimple => !IsAllowingMultipleEdges && !IsAllowingSelfLoops;

    public bool IsPseudograph => IsAllowingMultipleEdges && IsAllowingSelfLoops;

    public bool IsMultigraph => IsAllowingMultipleEdges && !IsAllowingSelfLoops;

    public IGraphType AsDirected() => new Builder(this).Directed().Build();

    public IGraphType AsUndirected() => new Builder(this).Undirected().Build();

    public IGraphType AsMixed() => new Builder(this).Mixed().Build();

    public IGraphType AsUnweighted() =>
        new Builder(this).Weighted(false).Build();

    public IGraphType AsWeighted() => new Builder(this).Weighted(true).Build();

    public IGraphType AsModifiable() =>
        new Builder(this).Modifiable(true).Build();

    public IGraphType AsUnmodifiable() =>
        new Builder(this).Modifiable(false).Build();

    public static DefaultGraphType Simple() =>
        new Builder().Undirected().AllowSelfLoops(false)
            .AllowMultipleEdges(false).Weighted(false).Build();

    public static DefaultGraphType Multigraph() =>
        new Builder().Undirected().AllowSelfLoops(false)
            .AllowMultipleEdges(true).Weighted(false).Build();

    public static DefaultGraphType Pseudograph() =>
        new Builder().Undirected().AllowSelfLoops(true).AllowMultipleEdges(true)
            .Weighted(false).Build();

    public static DefaultGraphType DirectedSimple() =>
        new Builder().Directed().AllowSelfLoops(false).AllowMultipleEdges(false)
            .Weighted(false).Build();

    public static DefaultGraphType DirectedMultigraph() =>
        new Builder().Directed().AllowSelfLoops(false).AllowMultipleEdges(true)
            .Weighted(false).Build();

    public static DefaultGraphType DirectedPseudograph() =>
        new Builder().Directed().AllowSelfLoops(true).AllowMultipleEdges(true)
            .Weighted(false).Build();

    public static DefaultGraphType Mixed() =>
        new Builder().Mixed().AllowSelfLoops(true).AllowMultipleEdges(true)
            .Weighted(false).Build();

    public static DefaultGraphType Dag() =>
        new Builder().Directed().AllowSelfLoops(false).AllowMultipleEdges(true)
            .AllowCycles(false).Weighted(false).Build();

    public override string ToString() =>
        $"DefaultGraphType [directed={_directed}, undirected={_undirected}, self-loops={_selfLoops}, multiple-edges={_multipleEdges}, weighted={_weighted}, allows-cycles={_allowsCycles}, modifiable={_modifiable}]";

    public class Builder {
        private bool _directed;
        private bool _undirected;
        private bool _allowSelfLoops;
        private bool _allowMultipleEdges;
        private bool _weighted;
        private bool _allowCycles;
        private bool _modifiable;

        public Builder() {
            _directed = false;
            _undirected = true;
            _allowSelfLoops = true;
            _allowMultipleEdges = true;
            _weighted = false;
            _allowCycles = true;
            _modifiable = true;
        }

        public Builder(IGraphType type) {
            _directed = type.IsDirected || type.IsMixed;
            _undirected = type.IsUndirected || type.IsMixed;
            _allowSelfLoops = type.IsAllowingSelfLoops;
            _allowMultipleEdges = type.IsAllowingMultipleEdges;
            _weighted = type.IsWeighted;
            _allowCycles = type.IsAllowingCycles;
            _modifiable = type.IsModifiable;
        }

        public Builder(bool directed, bool undirected) {
            if (!directed && !undirected) {
                throw new ArgumentException(
                    "At least one of directed or undirected must be true");
            }

            _directed = directed;
            _undirected = undirected;
            _allowSelfLoops = true;
            _allowMultipleEdges = true;
            _weighted = false;
            _allowCycles = true;
            _modifiable = true;
        }

        public Builder Directed() {
            _directed = true;
            _undirected = false;
            return this;
        }

        public Builder Undirected() {
            _directed = false;
            _undirected = true;
            return this;
        }

        public Builder Mixed() {
            _directed = true;
            _undirected = true;
            return this;
        }

        public Builder AllowSelfLoops(bool value) {
            _allowSelfLoops = value;
            return this;
        }

        public Builder AllowMultipleEdges(bool value) {
            _allowMultipleEdges = value;
            return this;
        }

        public Builder Weighted(bool value) {
            _weighted = value;
            return this;
        }

        public Builder AllowCycles(bool value) {
            _allowCycles = value;
            return this;
        }

        public Builder Modifiable(bool value) {
            _modifiable = value;
            return this;
        }

        public DefaultGraphType Build() =>
            new(_directed, _undirected, _allowSelfLoops, _allowMultipleEdges,
                _weighted, _allowCycles, _modifiable);
    }
}